// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 * Description: liblinux tty jobctrl
 * Author: Huawei OS Kernel Lab
 * Create: Fri Sep 23 11:33:17 2022
 */

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/tty.h>
#include <liblinux/unimpl.h>
#include "tty.h"

int __tty_check_change(struct tty_struct *tty, int sig)
{
	int sid;
	int pgid;
	int ret = 0;

	sid = liblinux_pal_get_sid();
	if (sid < 0)
		return -EIO;

	pgid = liblinux_pal_get_pgid();
	if (pgid < 0)
		return -EIO;

	if (sid != tty->hmsid) {
		pr_debug("tty: process does not belong to current tty, pid=%d, sid=%d\n",
				current->pid, sid);
		return 0;
	}

	if (tty->hmpgrp != 0 && pgid != tty->hmpgrp) {
		if (liblinux_pal_signal_isignored(pgid, sig)) {
			if (sig == SIGTTIN)
				ret = -EIO;

		} else if (liblinux_pal_current_pgrp_isorphaned()) {
			ret = -EIO;
		} else {
			tty_debug(tty, "send sig %d to pgid %d which is not foregound process\n",
				sig, -pgid);
			liblinux_pal_process_signal_with_pid(-pgid, sig);
			ret = -ERESTARTSYS;
		}
	}

	if (tty->hmpgrp == 0)
		tty_warn(tty, "sig=%d, tty->hmpgrp is NULL!\n", sig);

	return ret;
}

int tty_check_change(struct tty_struct *tty)
{
	return __tty_check_change(tty, SIGTTOU);
}
EXPORT_SYMBOL(tty_check_change);

/* the caller function should tty_lock*/
void tty_open_proc_set_tty(struct file *filp, struct tty_struct *tty)
{
	int rsid;
	int ret;
	struct tty_info info;

	rsid = liblinux_pal_get_sid();
	if (rsid < 0)
		return;

	if (rsid == tty->hmsid)
		return;

	if ((filp->f_mode & FMODE_READ) == 0)
		return;

	info.tty_sid = tty->hmsid;
	info.tty_dev_no = (unsigned int)tty_devnum(tty);
	info.tty_name = tty->name;
	info.f_mode = filp->f_mode & FMODE_READ;
	ret = liblinux_pal_set_control_terminal(&info, 0);
	if (ret == 0) {
		tty->hmsid = rsid;
		tty->hmpgrp = rsid;
	}
}

struct tty_struct *get_current_tty(void)
{
	return NULL;
}
EXPORT_SYMBOL_GPL(get_current_tty);

void session_clear_tty(struct pid *session)
{
}

int tty_signal_session_leader(struct tty_struct *tty, int exit_session)
{
	return 0;
}

static int tiocsctty(struct tty_struct *tty, struct file *file, int arg)
{
	int ret = 0;
	int rsid;
	struct tty_info info;

	rsid = liblinux_pal_get_sid();
	if (rsid < 0)
		return -ESRCH;

	info.tty_sid = tty->hmsid;
	info.tty_dev_no = (unsigned int)tty_devnum(tty);
	info.tty_name = tty->name;
	info.f_mode = file->f_mode & FMODE_READ;
	tty_lock(tty);
	ret = liblinux_pal_set_control_terminal(&info, arg);
	if (ret < 0) {
		tty_debug(tty, "set terminal failed, sid=%d, name=%s, num=%u, mode=%u, arg=%d\n",
			tty->hmsid, tty->name, (unsigned int)tty_devnum(tty), info.f_mode, arg);
	} else {
		tty->hmsid = rsid;
		tty->hmpgrp = rsid;
	}
	tty_unlock(tty);
	return ret;
}

struct pid *tty_get_pgrp(struct tty_struct *tty)
{
	return NULL;
}
EXPORT_SYMBOL_GPL(tty_get_pgrp);

static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
{
	int ret;
	int pgrp;
	int rsid;
	int tty_pgrp;

	rsid = liblinux_pal_get_sid();
	if (rsid < 0)
		return rsid;

	tty_lock(tty);
	if (rsid != tty->hmsid) {
		tty_unlock(tty);
		return -ENOTTY;
	}
	tty_pgrp = tty->hmpgrp;
	tty_unlock(tty);
	pgrp = liblinux_pal_get_pgrp(rsid);
	if (pgrp < 0)
		return pgrp;

	if (tty_pgrp != pgrp)
		return -EIO;

	ret = put_user((pid_t)pgrp, p);
	if (ret != 0)
		tty_debug(tty, "put_user failed, ret=%d\n", ret);

	return ret;
}

static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
{
	int ret;
	pid_t pgrp = 0;
	int rsid;

	if (get_user(pgrp, p))
		return -EFAULT;

	if (pgrp < 0)
		return -EINVAL;

	rsid = liblinux_pal_get_sid();
	if (rsid < 0)
		return rsid;

	tty_lock(tty);
	if (rsid != tty->hmsid) {
		tty_unlock(tty);
		return -ENOTTY;
	}
	tty_unlock(tty);

	ret = liblinux_pal_set_pgrp(rsid, (int)pgrp);
	if (ret != 0) {
		tty_err(tty, "cannot set pgrp, ret=%d\n", ret);
		return ret;
	}
	tty_lock(tty);
	tty->hmpgrp = (int)pgrp;
	tty_unlock(tty);
	return ret;
}

static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
{
	int rsid = liblinux_pal_get_sid();

	if (rsid < 0)
		return rsid;

	tty_lock(tty);
	if (rsid != tty->hmsid) {
		tty_unlock(tty);
		return -ENOTTY;
	}
	tty_unlock(tty);
	return put_user((pid_t)rsid, p);
}

long tty_jobctrl_ioctl(struct tty_struct *tty, struct tty_struct *real_tty,
		       struct file *file, unsigned int cmd, unsigned long arg)
{
	void __user *p = (void __user *)arg;

	/* liblinux currently does not support pty */
	BUG_ON(tty != real_tty);

	switch (cmd) {
	case TIOCSCTTY:
		return tiocsctty(real_tty, file, arg);
	case TIOCGPGRP:
		return tiocgpgrp(tty, real_tty, p);
	case TIOCSPGRP:
		return tiocspgrp(tty, real_tty, p);
	case TIOCGSID:
		return tiocgsid(tty, real_tty, p);
	}
	return -ENOIOCTLCMD;
}
